package com.example.financeapi.utils;

import cn.hutool.core.util.XmlUtil;
import com.thoughtworks.xstream.XStream;
import me.chanjar.weixin.common.util.xml.XStreamInitializer;
import org.apache.commons.lang3.StringUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.json.JSONObject;
import org.json.XML;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.*;

/**
 * XML转换工具
 * @author Alvin
 */
public class XMLUtils extends XmlUtil {

    public static void main(String[] args) {

        //xml转json
        String getXMl =getXMl("111key","222value");
        JSONObject xmlJSONObj= getXmlToJson(getXMl,true);
        System.out.println("target json : \t" + xmlJSONObj);
        System.out.println(getJsonObjToXmlStr(xmlJSONObj));
    }

    /**
     * 传入JSON字符串返回XML字符串
     * @param json
     * @return
     */
    public static String getJsonToXml(String json) {
        JSONObject jsonObj = new JSONObject(json);
        return "<xml>" + XML.toString(jsonObj) + "</xml>";
    }

    /**
     * 传入XML字符串返回JSON对象
     * @param xml
     * @return
     */
    public static JSONObject getXmlToJson(String xml,boolean flag) {
        JSONObject xmlJSONObj;
        if(flag){
            xmlJSONObj=	XML.toJSONObject(xml.replace("<?xml version='1.0' encoding='UTF-8'?>", "").replace("</xml>", ""));
        }else{
            xmlJSONObj=	XML.toJSONObject(xml.replace("<xml>", "").replace("</xml>", ""));
        }
        return xmlJSONObj;
    }

    /**
     * JSON对象转换成XML字符串
     * @param json
     * @return
     */
    public static String getJsonObjToXmlStr(JSONObject json){
        StringBuffer sb = new StringBuffer("<?xml version='1.0' encoding='UTF-8'?>");
        for(Object key : json.keySet()){
            sb.append("<").append(key).append(">");
            Object value = json.get((String) key);
            //判断value是否含有JSONArray
            JSONObject jsonV = ((JSONObject)value);
            for(Object key2 : jsonV.keySet()){
                sb.append("<").append(key2).append(">");
                Object value2 = jsonV.get((String) key2);
                sb.append(value2);
                sb.append("</").append(key2).append(">");
            }
            sb.append("</").append(key).append(">");
        }
        sb.append("</xml>");
        return sb.toString();
    }

    /**
     * 将String类型的xml转换成对象
     */
    public static Object convertXmlStrToObject(Class clazz, String xmlStr) {
        Object xmlObject = null;
        try {
            JAXBContext context = JAXBContext.newInstance(clazz);
            // 进行将Xml转成对象的核心接口
            Unmarshaller unmarshaller = context.createUnmarshaller();
            StringReader sr = new StringReader(xmlStr);
            xmlObject = unmarshaller.unmarshal(sr);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
        return xmlObject;
    }


    /**
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点，则此节点的值是子节点的xml数据。
     * @param strXml
     * @return
     * @throws JDOMException
     * @throws IOException
     */
    public static Map doXMLParse(String strXml) throws JDOMException, IOException {
        strXml = filterXXE(strXml);
        strXml = strXml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
        if (StringUtils.isBlank(strXml)) {
            return null;
        }
        Map m = new HashMap();
        InputStream in = new ByteArrayInputStream(strXml.getBytes("UTF-8"));
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(in);
        Element root = doc.getRootElement();
        List list = root.getChildren();
        Iterator it = list.iterator();
        while (it.hasNext()) {
            Element e = (Element) it.next();
            String k = e.getName();
            String v;
            List children = e.getChildren();
            if (children.isEmpty()) {
                v = e.getTextNormalize();
            } else {
                v = XMLUtils.getChildrenText(children);
            }

            m.put(k, v);
        }
        in.close();
        return m;
    }

    /**
     * 获取子结点的xml
     * @param children
     * @return String
     */
    public static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if (!children.isEmpty()) {
            Iterator it = children.iterator();
            while (it.hasNext()) {
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if (!list.isEmpty()) {
                    sb.append(XMLUtils.getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }

        return sb.toString();
    }

    /**
     * 通过DOCTYPE和ENTITY来加载本地受保护的文件、替换掉即可
     * 漏洞原理：https://my.oschina.net/u/574353/blog/1841103
          * 防止 XXE漏洞 注入实体攻击
          * 过滤 过滤用户提交的XML数据
          * 过滤关键词：<!DOCTYPE和<!ENTITY，或者SYSTEM和PUBLIC。
         */
    public static String filterXXE(String xmlStr){
        xmlStr = xmlStr.replace("DOCTYPE", "").replace("SYSTEM", "").replace("ENTITY", "").replace("PUBLIC", "");
        return xmlStr;
    }

    /**
     * 微信给出的 XXE漏洞方案
     * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_5
     * @param strXML
     * @return
     * @throws Exception
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static Map doXMLParse2(String strXML) throws Exception {
        Map<String,String> m = new HashMap<>();
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        String FEATURE = null;
        try {
            FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
            documentBuilderFactory.setFeature(FEATURE, true);

            FEATURE = "http://xml.org/sax/features/external-general-entities";
            documentBuilderFactory.setFeature(FEATURE, false);

            FEATURE = "http://xml.org/sax/features/external-parameter-entities";
            documentBuilderFactory.setFeature(FEATURE, false);

            FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
            documentBuilderFactory.setFeature(FEATURE, false);

            documentBuilderFactory.setXIncludeAware(false);
            documentBuilderFactory.setExpandEntityReferences(false);
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        }
        DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
        InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
        org.w3c.dom.Document doc = documentBuilder.parse(stream);
        doc.getDocumentElement().normalize();
        NodeList nodeList = doc.getDocumentElement().getChildNodes();
        for (int idx=0; idx<nodeList.getLength(); ++idx) {
            Node node = nodeList.item(idx);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                m.put(element.getNodeName(), element.getTextContent());
            }
        }
        stream.close();
        return m;
    }

    /**
     * obj 转 XML
     * @param object
     * @return
     */
    public static String toXML(Object object) {
        XStream xstream = XStreamInitializer.getInstance();
        xstream.processAnnotations(object.getClass());
        return xstream.toXML(object);
    }

    /**
     * 将请求参数转换为xml格式的string
     * @param parameters
     * @return
     */
    public static String getRequestXml(SortedMap<Object, Object> parameters) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
                sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
            } else {
                sb.append("<" + k + ">" + v + "</" + k + ">");
            }
        }
        sb.append("</xml>");
        return sb.toString();
    }

    /**
     * 测试得到XML
     * @param api_key
     * @return
     */
    public static String getXMl(String api_key,String sys_user){
        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version='1.0' encoding='UTF-8'?>");
        sb.append("<root>");
        sb.append("<api_key>"+api_key+"</api_key>");
        sb.append("<sys_user>"+sys_user+"</sys_user>");
        sb.append("</root>");
        return sb.toString();
    }
}
